package omg.UserInterfaces;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import omg.AddSong;
import omg.Configuration;
import omg.DbHandling.DatabaseInt;
import omg.EditStoreLocation;
import omg.Error.DBError;
import omg.Error.NoConnectionError;
import omg.Error.NoValidConverterError;
import omg.Error.NoValidParserError;
import omg.Error.NoValidStreamError;
import omg.Error.OmgError;
import omg.Error.PersistanceError;
import omg.Error.PhysicalStoreError;
import omg.Error.SongCreationError;
import omg.Error.StoreError;
import omg.Persistance.PersistanceInt;
import omg.SongHandling.DataHandling.Song;
import omg.SongHandling.DataHandling.SongManager;
import omg.StoreHandling.StoreManager;

public class UserInt implements PropertyChangeListener {
    private static UserInt instance = null;
    public static String[] genres = {"Blues","Classic Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop","Jazz","Metal","New Age","Oldies","Other","Pop","R&B","Rap","Reggae","Rock","Techno","Industrial","Alternative","Ska","Death Metal","Pranks","Soundtrack","Euro-Techno","Ambient","Trip-Hop","Vocal","Jazz+Funk","Fusion","Trance","Classical","Instrumental","Acid","House","Game","Sound Clip","Gospel","Noise","Alternative Rock","Bass","Soul","Punk","Space","Meditative","Instrumental Pop","Instrumental Rock","Ethnic","Gothic","Darkwave","Techno-Industrial","Electronic","Pop-Folk","Eurodance","Dream","Southern Rock","Comedy","Cult","Gangsta","Top 40","Christian Rap","Pop/Funk","Jungle","Native US","Cabaret","New Wave","Psychadelic","Rave","Showtunes","Trailer","Lo-Fi","Tribal","Acid Punk","Acid Jazz","Polka","Retro","Musical","Rock & Roll","Hard Rock","Folk","Folk-Rock","National Folk","Swing","Fast Fusion","Bebob","Latin","Revival","Celtic","Bluegrass","Avantgarde","Gothic Rock","Progressive Rock","Psychedelic Rock","Symphonic Rock","Slow Rock","Big Band","Chorus","Easy Listening","Acoustic","Humour","Speech","Chanson","Opera","Chamber Music","Sonata","Symphony","Booty Bass","Primus","Porn Groove","Satire","Slow Jam","Club","Tango","Samba","Folklore","Ballad","Power Ballad","Rhythmic Soul","Freestyle","Duet","Punk Rock","Drum Solo","Acapella","Euro-House","Dance Hall","Goa","Drum & Bass","House","Hardcore","Terror","Indie","BritPop","Negerpunk","Polsk Punk","Beat","Christian Gangsta Rap","Heavy Metal","Black Metal","Crossover","Contemporary Christian","Christian Rock","Merengue","Salsa","Thrash Metal","Anime","JPop","Synthpop","Unknown"};

    private UserInt uio = null;
    private AddSong aso = null;
    private Configuration coo = null;
    private EditStoreLocation esl = null;
    private Song addSong_song = null;

    /* SINGLETON PATTERN */
    private UserInt() {
        this.uio = this;
        // !TODO: THIS MAKES NO SENSE
        try {
            Logger.getLogger("Information").addHandler(new FileHandler("information.log"));
            Logger.getLogger("Information").setLevel(Level.ALL);
            Logger.getLogger("Core").addHandler(new FileHandler("core.log"));
            Logger.getLogger("Core").setLevel(Level.ALL);
            Logger.getLogger("Periphery").addHandler(new FileHandler("periphery.log"));
            Logger.getLogger("Periphery").setLevel(Level.ALL);
        } catch (IOException ex) {
            Logger.getLogger(UserInt.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SecurityException ex) {
            Logger.getLogger(UserInt.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    public static UserInt getInstance() {
        if (null == UserInt.instance)
            UserInt.instance = new UserInt();
        return UserInt.instance;
    }

    /* Edit StoreLocation CONTROLLER */

    public void registerChangeStoreLocation(EditStoreLocation esl) {
        this.esl = esl;
        this.initializeChangeStoreLocation();
        esl.saveButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                uio.setStoreLocation();
            }
        });
    }

    private void setStoreLocation() {
        try {
            File current_location = new File(DatabaseInt.getInstance().getConfigValue("StoreLocation"));
            File new_location     = new File(esl.storeLocation.getText());
            StoreManager.getInstance().alterStoreLocation(current_location, new_location);
            JOptionPane.showMessageDialog(esl, "Store location changed", "Success", JOptionPane.INFORMATION_MESSAGE);
            esl.dispose();
        }
        catch (DBError ex) {
            Logger.getLogger("Information").log(Level.WARNING, "Getting current store location failed", ex);
            JOptionPane.showMessageDialog(esl, "Could not change the store location, getting the config failed", "Error", JOptionPane.ERROR_MESSAGE);
        }
        catch (PhysicalStoreError ex) {
            Logger.getLogger("Information").log(Level.WARNING, "Could not change the store location, Physical problem.", ex);
            JOptionPane.showMessageDialog(esl, "Storage could not be moved", "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    private void initializeChangeStoreLocation() {
        try {
            this.esl.storeLocation.setText(DatabaseInt.getInstance().getConfigValue("StoreLocation"));
        }
        catch (DBError e) {
            Logger.getLogger("Information").log(Level.WARNING, "Setting current storelocation path problem", e);
        }
    }

    /* AddSong CONTROLLER */
    /**
     * Register an AddSong object to the user interface controller
     * @param aso the AddSong object
     */
    public void registerAddSong(AddSong aso) {
        this.aso = aso;
        this.initializeAddSong();
        aso.buttonParse.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {uio.addSongButtonParsePressed();}
        });
        aso.buttonSave.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {uio.addSongButtonSavePressed();}
        });
        aso.link.getDocument().addDocumentListener(new DocumentListener() {
                @Override
                public void insertUpdate(DocumentEvent e) {uio.addSongLinkChanged();}
                @Override
                public void removeUpdate(DocumentEvent e) {uio.addSongLinkChanged();}
                @Override
                public void changedUpdate(DocumentEvent e) {}
        });
    }

    /**
     * Unregister an AddSong object from the user interface controller.
     */
    public void unregisterAddSong() {
        this.addSongCleanup();
    }

    public void setProgressState(int progress) {
        if (100 == progress) {
            aso.progressBar.setVisible(false);
        } else {
            System.out.println("Refresh prgressBar: " + progress);
            aso.progressBar.setVisible(true);
            aso.progressBar.setValue(progress);
            aso.update(aso.progressBar.getGraphics());
            aso.repaint();
        }
    }

    /**
     * Initializes all fields.
     */
    private void initializeAddSong() {
        aso.link.setEnabled(true);
        aso.source.setText("");
        aso.source.setEnabled(false);
        aso.format.setText("");
        aso.format.setEnabled(false);
        aso.notices.setText("Please supply a source link and parse.");
        aso.notices.setEnabled(false);
        aso.artist.setText("");
        aso.artist.setEnabled(false);
        aso.album.setText("");
        aso.album.setEnabled(false);
        aso.title.setText("");
        aso.title.setEnabled(false);
        aso.year.setText("");
        aso.year.setEnabled(false);
        aso.genre.removeAllItems();
        for (String genre: genres)
            aso.genre.addItem(genre);
        aso.genre.setEnabled(false);

        aso.buttonParse.setEnabled(true);
        aso.buttonCancle.setEnabled(true);
        aso.buttonSave.setEnabled(true);
        aso.progressBar.setVisible(false);
    }
    /**
     * Gives a notice if the source link is changed after parsing.
     */
    private void addSongLinkChanged() {
        if (aso.title.isEnabled())
            aso.notices.setText("Please reparse if changed.");
    }
    /**
     * Checks wheher the source is valid and the system is able to handle it.
     */
    private void addSongButtonParsePressed() {
        initializeAddSong();
        //ProgressBarDialog pbd = new ProgressBarDialog(null, true);
        //pbd.setTitle("Parsing ... ");
        //pbd.setVisible(true);
        //aso.setEnabled(false);
        aso.notices.setText("Parsing song in progress. Please wait...");
        try {
            // !Notice: Bad course, since doesn't handle multiple link sources
            this.addSong_song = SongManager.getInstance().createSong(new URI(aso.link.getText()))[0];
            aso.source.setText(this.addSong_song.getParser().getType());
            aso.format.setText(this.addSong_song.getStream().getType());
            aso.title.setText(this.addSong_song.getTitle());
            aso.artist.setText(this.addSong_song.getArtist());
            aso.album.setText(this.addSong_song.getAlbum());
            aso.genre.setSelectedItem(this.addSong_song.getGenre());
            aso.year.setText(this.addSong_song.getYear());
            aso.title.setEnabled(true);
            aso.artist.setEnabled(true);
            aso.album.setEnabled(true);
            aso.genre.setEnabled(true);
            aso.year.setEnabled(true);
        } catch (URISyntaxException ex) {
            aso.notices.setText("Link is not a valid URI.");
        } catch (NoConnectionError ex) {
            aso.notices.setText("Couldn't establish a connection.");
        } catch (SongCreationError ex) {
            if (NoValidParserError.class == ex.getCause().getClass())
                aso.notices.setText("No parser exists for this URI.");
            else if (NoValidStreamError.class == ex.getCause().getClass())
                aso.notices.setText("No Stream exists for this URI.");
            else if (NoValidConverterError.class == ex.getCause().getClass())
                aso.notices.setText("No converter exists for this URI.");
            else
                aso.notices.setText("An unexpected error occured. See log file.");
        } catch (OmgError ex) {
            aso.notices.setText("An unexpected error occured. See log file.");
        //} catch (LoggingError ex) {
        //    aso.notices.setText("The logging facility is broken! Please inform the Admin.");
        } catch (Exception ex) {
            Logger.getLogger("Information").log(Level.SEVERE, "Unknwon error while parsing.", ex);
            aso.notices.setText("An fatal error occured. See log file.");
        }
        //pbd.dispose();
        //aso.setEnabled(true);
    }
    /**
     * Checks whether all required informations were supplied and if, returns
     * true. Otherwise false and sets the notice.
     */
    private boolean addSongCheckId3Fields() {
        if (aso.artist.getText().trim().length() < 3)
            aso.notices.setText("The artist must at least be 3 characters long.");
        else if (aso.album.getText().trim().length() < 3)
            aso.notices.setText("The album must at least be 3 characters long.");
        else if (aso.title.getText().trim().length() < 3)
            aso.notices.setText("The title must at least be 3 characters long.");
        else if (aso.year.getText().trim().length() < 4)
            aso.notices.setText("The year must at least be 4 characters long.");
        else
            return true;
        return false;
    }
    /**
     * Handles the checking of the supplied data and the saving of the song.
     */
    private void addSongButtonSavePressed() {
        try {
            if (!this.addSongCheckId3Fields()) {
                return;
            }
            
            this.aso.notices.setText("Adding song in progress. Please wait...");
            this.addSong_song.setTitle(aso.title.getText());
            this.addSong_song.setArtist(aso.artist.getText());
            this.addSong_song.setAlbum(aso.album.getText());
            this.addSong_song.setGenre(String.valueOf(aso.genre.getSelectedIndex()));
            this.addSong_song.setYear(aso.year.getText());

            if (DatabaseInt.getInstance().isAutoPersist()) {
                
                PersistanceInt persistence = new PersistanceInt();
                persistence.addPropertyChangeListener(this);
                persistence.setSong(this.addSong_song);
                persistence.setName(StoreManager.getInstance().getSongFileName(this.addSong_song));
                persistence.execute();
                
            } else {
                //DatabaseInt.getInstance().store(this.addSong_song);
                throw new UnsupportedOperationException();
            }
        } catch (DBError ex) {
            JOptionPane.showMessageDialog(aso, "Song couldn't be saved! A database error occured!  See log file for more details.", "Error", JOptionPane.ERROR_MESSAGE);
        }
    }
    /**
     * Clean up.
     */
    private void addSongCleanup() {
        this.addSong_song = null;
        this.aso = null;
    }

    /* Configuration CONTROLLER */
    public void registerConfiguration (Configuration coo) {
        this.coo = coo;
        this.initializeConfiguration();
        coo.buttonSave.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {uio.configurationButtonSavePressed();}
        });
        coo.buttonCancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {uio.configurationButtonCancelPressed();}
        });
    }

    public void unregisterConfiguration () {
        this.coo = null;
    }

    private void configurationButtonCancelPressed() {
        this.unregisterConfiguration();
    }

    private void initializeConfiguration() {
        DatabaseInt db = DatabaseInt.getInstance();
        try {
            coo.stringRepresentation.setText(db.getStringRepresentationPattern());
            coo.youdlLocation.setText(db.getConfigValue("youdlLocation"));
            coo.ffmpegLocation.setText(db.getConfigValue("ffmpegLocation"));
        } catch (DBError ex) {
            Logger.getLogger("Periphery").log(Level.WARNING, "Couldn't get values form the database.", ex);
            JOptionPane.showMessageDialog(aso, "Couldn't get values form the database.  See log file for more details.", "Error", JOptionPane.ERROR_MESSAGE);
            this.coo.dispose();
            this.unregisterConfiguration();
        }
    }

    private void configurationButtonSavePressed() {
        System.out.println("Validate configuration");
//        if (this.configurationValidateStringRepresentation() &&
//            this.configurationValidateYoudlLocation() &&
//            this.configurationValidateFfmpegLocation()) {
            DatabaseInt db = DatabaseInt.getInstance();
            try {
                db.setConfigValue("StringRepresentation", coo.stringRepresentation.getText());
                db.setConfigValue("youdlLocation", coo.youdlLocation.getText());
                db.setConfigValue("ffmpegLocation", coo.ffmpegLocation.getText());
                this.coo.dispose();
                this.unregisterConfiguration();
            } catch (DBError ex) {
                Logger.getLogger("Periphery").log(Level.WARNING, "Couldn't store values in the database.", ex);
                JOptionPane.showMessageDialog(aso, "Couldn't store values in the database. See log file for more details.", "Error", JOptionPane.ERROR_MESSAGE);
                this.coo.dispose();
                this.unregisterConfiguration();
            }
//        }   
    }

    private boolean configurationValidateStringRepresentation() {
        System.out.println("Property: " + System.getProperty("file.separator"));
        String[] elements = this.coo.stringRepresentation.getText().split(System.getProperty("file.separator"));
        if (-1 == elements[elements.length - 1].indexOf("%title%")) {
            this.coo.notices.setText("StringRepresentation: %title% must be both used in the filename.");
            return false;
        } else
            return true;
    }

    private boolean configurationValidateYoudlLocation() {
        File f = new File(this.coo.youdlLocation.getText().toString());
        if (!f.exists()) {
            this.coo.notices.setText("YoudlLocation: Target file doesn't exist.");
            return false;
        } else
            return true;
    }

    private boolean configurationValidateFfmpegLocation() {
        File f = new File(this.coo.ffmpegLocation.getText().toString());
        if (!f.exists()) {
            this.coo.notices.setText("FfmpegLocation: Target file doesn't exist.");
            return false;
        } else
            return true;
    }

    public void propertyChange(PropertyChangeEvent evt) {
        if ("progress" == evt.getPropertyName()) {
            int progress = (Integer) evt.getNewValue();
            this.aso.progressBar.setValue(progress);
        } else if ( "done" == evt.getPropertyName()) {
            boolean done = (Boolean) evt.getNewValue();
            if (done) {
                this.aso.progressBar.setValue(100);
                JOptionPane.showMessageDialog(aso, "Saving song finished", "Done", JOptionPane.INFORMATION_MESSAGE);
                this.initializeAddSong();
            }
        }
    }
}